#include "vtsreadfilter.hpp"
#include <new>
#include <cassert>
#include <sstream>
#include <algorithm>
#include <vfwmsgs.h>
#include "cenumpins.hpp"
#ifdef _DEBUG
#include "odbgstream.hpp"
using std::endl;
#endif

using std::wstring;
using std::wistringstream;

namespace VTSRead
{

// {AEEA8CAC-9FDA-49ca-8001-1B984E8F1663}
extern const GUID CLSID_VTSRead = 
{ 0xaeea8cac, 0x9fda, 0x49ca, { 0x80, 0x1, 0x1b, 0x98, 0x4e, 0x8f, 0x16, 0x63 } };


Filter::FileInfo::FileInfo() : m_size(0), m_hFile(0)
{
}


//File::FileInfo::FileInfo(const WIN32_FIND_DATA& fd) :
//    m_path(fd.cFileName),
//    m_size((__int64(fd.nFileSizeHigh) << 32) + fd.nFileSizeLow),
//    m_hFile(0)
//{
//}


Filter::FileInfo::FileInfo(const FileInfo& rhs) :
    m_path(rhs.m_path),
    m_size(rhs.m_size),
    m_hFile(rhs.m_hFile)
{
}


//Filter::FileInfo::operator=(const WIN32_FIND_DATA& fd)
//{
//    assert(m_path.empty());
//    assert(m_size == 0);
//    assert(m_hFile == 0);
//    
//    m_path = fd.cFileName;
//    m_size = __int64(fd.nFileSizeHigh) << 32) + fd.nFileSizeLow;
//}


Filter::Lock::Lock() : m_hMutex(0)
{
}


Filter::Lock::~Lock()
{
    Release();
}


HRESULT Filter::Lock::Seize(Filter* pFilter, DWORD timeout)
{
    assert(m_hMutex == 0);
    assert(pFilter);
    
#if 0
    const HANDLE hMutex = pFilter->m_hMutex;
    
    const DWORD dw = WaitForSingleObject(hMutex, timeout);
    
    if (dw == WAIT_FAILED)
    {
        const DWORD e = GetLastError();
        return HRESULT_FROM_WIN32(e);
    }
    
    if (dw == WAIT_TIMEOUT)
        return VFW_E_TIMEOUT;

    m_hMutex = hMutex;
#else
    DWORD index;

    const HRESULT hr = CoWaitForMultipleHandles(
                            0,  //wait flags
                            timeout, 
                            1, 
                            &pFilter->m_hMutex, 
                            &index);

    //despite the "S" in this name, this is an error                        
    if (hr == RPC_S_CALLPENDING)
        return VFW_E_TIMEOUT;

    if (FAILED(hr))
        return hr;
                
    assert(index == 0);

    m_hMutex = pFilter->m_hMutex;
#endif
        
    return S_OK;
}


void Filter::Lock::Release()
{
    if (m_hMutex)
    {
        const BOOL b = ReleaseMutex(m_hMutex);
        assert(b);
        b;
        
        m_hMutex = 0;
    }
}



HRESULT CreateInstance(
    IClassFactory* pClassFactory,
    IUnknown* pOuter, 
    const IID& iid, 
    void** ppv)
{
    if (ppv == 0)
        return E_POINTER;
        
    *ppv = 0;

    if ((pOuter != 0) && (iid != __uuidof(IUnknown)))
        return E_INVALIDARG;
    
    Filter* p = new (std::nothrow) Filter(pClassFactory, pOuter);
    
    if (p == 0)
        return E_OUTOFMEMORY;
        
    assert(p->m_nondelegating.m_cRef == 0);
    
    const HRESULT hr = p->m_nondelegating.QueryInterface(iid, ppv);
    
    if (SUCCEEDED(hr))
    {
        assert(*ppv);
        assert(p->m_nondelegating.m_cRef == 1);
        
        return S_OK;
    }
    
    assert(*ppv == 0);
    assert(p->m_nondelegating.m_cRef == 0);

    delete p;
    p = 0;

    return hr;
}


#pragma warning(disable:4355)  //'this' ptr in member init list
Filter::Filter(IClassFactory* pClassFactory, IUnknown* pOuter)
    : m_pClassFactory(pClassFactory),
      m_nondelegating(this),
      m_pOuter(pOuter ? pOuter : &m_nondelegating),
      m_state(State_Stopped),
      m_clock(0),
      //m_pGraph(0),
      m_outpin(this)
      //m_hFile(0),
      //m_eof(false)
{
    m_pClassFactory->LockServer(TRUE);
            
    m_hMutex = CreateMutex(0, 0, 0);
    assert(m_hMutex);  //TODO
    
    m_info.pGraph = 0;
    m_info.achName[0] = L'\0';
    
#ifdef _DEBUG        
    odbgstream os;
    os << "vtsread::ctor" << endl;
#endif    
}
#pragma warning(default:4355)


Filter::~Filter()
{
#ifdef _DEBUG
    odbgstream os;
    os << "vtsread::dtor" << endl;
#endif

    const BOOL b = CloseHandle(m_hMutex);
    assert(b);
    b;

    while (!m_files.empty())
    {
        FileInfo& f = m_files.back();
        
        if (f.m_hFile)
        {
            const BOOL b = CloseHandle(f.m_hFile);
            assert(b);
            b;
        }
        
        m_files.pop_back();        
    }
    
    m_pClassFactory->LockServer(FALSE);
}      



Filter::nondelegating_t::nondelegating_t(Filter* p)
    : m_pFilter(p),
      m_cRef(0)  //see CreateInstance
{
}


Filter::nondelegating_t::~nondelegating_t()
{
}


HRESULT Filter::nondelegating_t::QueryInterface(
    const IID& iid, 
    void** ppv)
{
    if (ppv == 0)
        return E_POINTER;
        
    IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
     
    if (iid == IID_IUnknown)    
    {
        pUnk = this;  //must be nondelegating
    }   
    else if ((iid == IID_IBaseFilter) ||
             (iid == IID_IMediaFilter) ||
             (iid == IID_IPersist))
    {
        pUnk = static_cast<IBaseFilter*>(m_pFilter);
    }
    else if (iid == __uuidof(IFileSourceFilter))
    {
        pUnk = static_cast<IFileSourceFilter*>(m_pFilter);
    }
    //else if (iid == __uuidof(IAMFilterMiscFlags))
    //{
    //    pUnk = static_cast<IAMFilterMiscFlags*>(m_filter);
    //}
    else
    {
#if 0
        wodbgstream os;
        os << "mp3source::filter::QI: iid=" << IIDStr(iid) << std::endl;
#endif        
        pUnk = 0;
        return E_NOINTERFACE;
    }

    pUnk->AddRef();
    return S_OK;
}


ULONG Filter::nondelegating_t::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

    
ULONG Filter::nondelegating_t::Release()
{
    if (LONG n = InterlockedDecrement(&m_cRef))
        return n;
    
    delete m_pFilter;
    return 0;
}


HRESULT Filter::QueryInterface(const IID& iid, void** ppv)
{
    return m_pOuter->QueryInterface(iid, ppv);
}


ULONG Filter::AddRef()
{
    return m_pOuter->AddRef();
}


ULONG Filter::Release()
{
    return m_pOuter->Release();
}


HRESULT Filter::GetClassID(CLSID* p)
{
    if (p == 0)
        return E_POINTER;
        
    *p = CLSID_VTSRead;
    return S_OK;
}



HRESULT Filter::Stop()
{
    //Stop is a synchronous operation: when it completes,
    //the filter is stopped.

    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;

    m_state = State_Stopped;            
    return S_OK;
}


HRESULT Filter::Pause()
{
    //Unlike Stop(), Pause() can be asynchronous (that's why you have
    //GetState()).  We could use that here to build the samples index.    

    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
        
    m_state = State_Paused;            
    return S_OK;
}


HRESULT Filter::Run(REFERENCE_TIME start)
{
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
    
    m_start = start;
    m_state = State_Running;

    return S_OK;
}


HRESULT Filter::GetState( 
    DWORD /* timeout */ ,
    FILTER_STATE* p)
{
    if (p == 0)
        return E_POINTER;
        
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (SUCCEEDED(hr))
    {
        *p = m_state;
        return S_OK;
    }
    
    if (hr == VFW_E_TIMEOUT)
        return VFW_S_STATE_INTERMEDIATE;
    
    return hr;
}



HRESULT Filter::SetSyncSource( 
    IReferenceClock* clock)
{
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;

    if (m_clock)
        m_clock->Release();
        
    m_clock = clock;
    
    if (m_clock)
        m_clock->AddRef();

    return S_OK;
}


HRESULT Filter::GetSyncSource( 
    IReferenceClock** pclock)
{
    if (pclock == 0)
        return E_POINTER;
        
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;

    IReferenceClock*& clock = *pclock;
        
    clock = m_clock;
    
    if (clock)
        clock->AddRef();

    return S_OK;
}


HRESULT Filter::EnumPins(IEnumPins** pp)
{
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;

    IPin* pa[1] = { &m_outpin };
    
    return CEnumPins::CreateInstance(pa, 1, pp);
}



HRESULT Filter::FindPin( 
    LPCWSTR id,
    IPin** pp)
{
    if (id == 0)
        return E_POINTER;
        
    if (pp == 0)
        return E_POINTER;
        
    IPin*& p = *pp;
        
    if (wcscmp(id, m_outpin.m_id) == 0)
        p = &m_outpin;
    else
    {
        p = 0;
        return VFW_E_NOT_FOUND;
    }
        
    p->AddRef();
    return S_OK;
}



HRESULT Filter::QueryFilterInfo(FILTER_INFO* p)
{
    if (p == 0)
        return E_POINTER;
        
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;

    enum { size = sizeof(p->achName)/sizeof(WCHAR) };
    const errno_t e = wcscpy_s(p->achName, size, m_info.achName);
    assert(e == 0);
    e;

    p->pGraph = m_info.pGraph;
    
    if (p->pGraph)
        p->pGraph->AddRef();
        
    return S_OK;
}

HRESULT Filter::JoinFilterGraph( 
    IFilterGraph *pGraph,
    LPCWSTR name)
{
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
    
    //NOTE: 
    //No, do not adjust reference counts here!
    //Read the docs for the reasons why.
    //ENDNOTE.    
    
    m_info.pGraph = pGraph;

    if (name == 0)
        m_info.achName[0] = L'\0';
    else
    {
        enum { size = sizeof(m_info.achName)/sizeof(WCHAR) };
        const errno_t e = wcscpy_s(m_info.achName, size, name);
        assert(e == 0);  //TODO
        e;
    }
    
    return S_OK;
}


HRESULT Filter::QueryVendorInfo(LPWSTR* pstr)
{
    if (pstr == 0)
        return E_POINTER;
        
    wchar_t*& str = *pstr;
    
    str = 0;
    return E_NOTIMPL;
}


HRESULT Filter::Load(LPCOLESTR filename, const AM_MEDIA_TYPE* pmt)
{
    pmt;  //TODO: not sure what to do with this
    
    //IIRC you can add a media type and subtype to the area
    //of the registry where you establish the binding of file
    //extension and source filter, and the MT arg just states
    //the values pulled from the registry
    
    if (filename == 0)
        return E_INVALIDARG;  //?
        
    wstring path;
    wstring::size_type filepos;
    
    DWORD buflen = _MAX_PATH + 1;
    
    for (;;)
    {
        const DWORD cb = buflen * sizeof(wchar_t);
        wchar_t* buf = (wchar_t*)_alloca(cb);        
        wchar_t* ptr;
    
        const DWORD n = GetFullPathName(filename, buflen, buf, &ptr);
        
        if (n == 0)
        {
            const DWORD e = GetLastError();
            return HRESULT_FROM_WIN32(e);
        }
        
        if (n < buflen)
        {
            path = buf;
            filepos = ptr - buf;
            
            break;
        }
        
        buflen = 2 * buflen + 1;
    }
        
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
        
    if (!m_files.empty())  //already open
        return E_FAIL;  //?
        
    //TODO: we really need to use a pattern-matching library here
    
    //TODO: for now, require the first vts file: VTS_XX_YY.vob
    
    const wchar_t* const pathstr = path.c_str();
    const wchar_t* const file = pathstr + filepos;
    const wchar_t* const ext = wcsrchr(file, L'.');
    
    if (ext == 0)
        return E_INVALIDARG;
        
    if (_wcsicmp(ext, L".vob") == 0)
        __noop;
        
    else if (_wcsicmp(ext, L".mpg") == 0)
        __noop;
        
    else
        return E_INVALIDARG;
        
    //012345678901
    //VTS_X_Y.VOB
        
    if ((wcslen(file) < 11) || (_wcsnicmp(file, L"vts_", 4) != 0))
        return E_INVALIDARG;
        
    const wchar_t* const title = file + 4;        
    const wchar_t* str = wcschr(title, L'_');
    
    if ((str == 0) || ((str - title) <= 0))
        return E_INVALIDARG;
                
    const wchar_t* ptr = title;
    
    while (ptr != str)
    {
        if (!iswdigit(*ptr++))
            return E_INVALIDARG;
    }
    
    const wchar_t* const idxstr = str + 1;
    const ptrdiff_t idxlen = ext - idxstr;
    
    if (idxlen <= 0)
        return E_INVALIDARG;
        
    if (idxlen > 2)  //weird
        return E_INVALIDARG;
        
    ptr = idxstr;
    
    while (ptr != ext)
    {
        if (!iswdigit(*ptr++))
            return E_INVALIDARG;
    }
    
    wstring re(pathstr, idxstr);
    
    re.append(idxlen, L'?');
    re.append(ext);
         
    WIN32_FIND_DATA fd;
       
    const HANDLE h = FindFirstFile(re.c_str(), &fd);
    
    if (h == INVALID_HANDLE_VALUE)
    {
        const DWORD e = GetLastError();
        return HRESULT_FROM_WIN32(e);
    }
    
    m_files.reserve(99);  //vob index 1 - 99

#ifdef _DEBUG    
    wodbgstream os;
#endif
    
    for (;;)
    {
#ifdef _DEBUG
        os << L"vtsread::load: findfile file=" << fd.cFileName << endl;
#endif
        
        const size_t len = wcslen(fd.cFileName);
        
        if (len < 11)  //weird
        {
            FindClose(h);
            m_files.clear();
            
            return E_INVALIDARG;
        }
        
        const wchar_t* const end = fd.cFileName + len - 4;
        const wchar_t* const begin = end - idxlen;
        
        wistringstream is(wstring(begin, end));
        
        int idx;
        
        if (!(is >> idx) || (idx <= 0) || (idx >= 100))
        {
            FindClose(h);
            m_files.clear();

            return E_INVALIDARG;
        }
        
        if (unsigned(idx) > m_files.size())
            m_files.resize(idx, FileInfo());

        FileInfo& f = m_files[idx - 1];
        assert(f.m_path.empty());
        assert(f.m_size == 0);
        assert(f.m_hFile == 0);
                         
        f.m_path.assign(path, 0, filepos);
        f.m_path.append(fd.cFileName);

#ifdef _DEBUG
        os << L"vtsread::load: findfile path=" << f.m_path << endl;
#endif

        f.m_size = (__int64(fd.nFileSizeHigh) << 32) + fd.nFileSizeLow;

        const BOOL bMore = FindNextFile(h, &fd);
        
        if (bMore)
            continue;
            
        const DWORD e = GetLastError();
        
        if (e == ERROR_NO_MORE_FILES)
            break;
            
        FindClose(h);
        m_files.clear();

        return HRESULT_FROM_WIN32(e);
    }        

    const BOOL bClose = FindClose(h);
    assert(bClose);
    bClose;
    
    //check for gaps in index values
    
    typedef files_t::iterator iter_t;
    
    iter_t iter = m_files.begin();
    const iter_t iter_end = m_files.end();
    
    while (iter != iter_end)
    {
        const FileInfo& f = *iter++;
        assert(f.m_hFile == 0);
        
        if (f.m_path.empty())
        {
            m_files.clear();
            return E_INVALIDARG;
        }
    }
    
    iter = m_files.begin();
    DWORD e = 0;
    
    m_length = 0;
    
    while (iter != iter_end)
    {
        FileInfo& f = *iter++;
        assert(f.m_hFile == 0);
        
        const HANDLE hFile = CreateFile(
                                f.m_path.c_str(),
                                GENERIC_READ,
                                FILE_SHARE_READ,
                                0,
                                OPEN_EXISTING,
                                FILE_ATTRIBUTE_READONLY | 
                                  FILE_FLAG_SEQUENTIAL_SCAN,
                                0);
                        
        if (hFile == INVALID_HANDLE_VALUE)
        {
            e = GetLastError();
            break;
        }
        
        f.m_hFile = hFile;

        f.m_pos = m_length;
        m_length += f.m_size;
    }
        
    if (e)
    {
        while (!m_files.empty())
        {
            FileInfo& f = m_files.back();
            
            if (f.m_hFile)
            {
                const BOOL b = CloseHandle(f.m_hFile);
                assert(b);
                b;
            }
            
            m_files.pop_back();
        }
        
        return HRESULT_FROM_WIN32(e);
    }
    
    m_filename = filename;
    
    return S_OK;
}

    
HRESULT Filter::GetCurFile(LPOLESTR* pname, AM_MEDIA_TYPE* pmt)
{
    if (pmt)
        memset(pmt, 0, sizeof(AM_MEDIA_TYPE));  //TODO
        
    if (pname == 0)
        return E_POINTER;
        
    wchar_t*& name = *pname;
    name = 0;
    
    Lock lock;
    
    HRESULT hr = lock.Seize(this);
    
    if (FAILED(hr))
        return hr;
    
    if (m_filename.empty())
        return S_FALSE;
        
    const size_t len = m_filename.length();
    const size_t size = len + 1;
    const size_t cb = size * sizeof(wchar_t);
    
    name = (wchar_t*)CoTaskMemAlloc(cb);
    
    if (name == 0)
        return E_OUTOFMEMORY;
        
    const errno_t e = wcscpy_s(name, size, m_filename.c_str());
    assert(e == 0);
    e;
    
    return S_OK;
}


long Filter::Read(
    __int64 pos,
    long len,
    BYTE* ptr)
{
    assert(pos >= 0);
    assert(ptr);
    assert(!m_files.empty());  
                
    if (pos >= m_length)
        return 0;
        
    if (len <= 0)
        return 0;
        
    const long len_ = len;
      
    typedef files_t::const_iterator iter_t;

    const iter_t i = m_files.begin();
    const iter_t j = m_files.end();
    
    iter_t k = std::upper_bound(i, j, pos, ub_pred_t());
    assert(k != i);
    
    --k;
    
    while (k != j)
    {
        assert(len > 0);
        assert(pos >= k->m_pos);
        
        const FileInfo& f = *k++;
        assert((k == j) || (pos < k->m_pos));
        
        const __int64 off = pos - f.m_pos;
        assert(off >= 0);
        
        LARGE_INTEGER d;
        d.QuadPart = off;
        
        BOOL b = SetFilePointerEx(f.m_hFile, d, 0, FILE_BEGIN);
        assert(b);
        
        const __int64 n = f.m_size - off;
        
        DWORD m;
            
        if (n >= len)
        {
            b = ReadFile(f.m_hFile, ptr, len, &m, 0);
            
            if (!b)
                return -1;  //error
                
            assert(m >= DWORD(len));                

            return len_;  //done, all bytes read
        }
        
        const DWORD nn = static_cast<DWORD>(n);
        
        b = ReadFile(f.m_hFile, ptr, nn, &m, 0);
        
        if (!b)
            return -1;  //error
            
        assert(m >= nn);
        
        ptr += nn;
        pos += nn;
        len -= nn;
    }
    
    assert(len >= 0);    
    return len_ - len;
}


} //end namespace VTSRead
